﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Castle.ActiveRecord;
using Castle.ActiveRecord.Queries;
using Matriculex.Dominio.Util;

namespace Matriculex.Dominio
{
    [ActiveRecord("aluno_aberto"), Serializable]
    public class AlunoAberto : Aluno
    {
        // construtor vazio necessário para o ActiveRecord
        protected AlunoAberto() { }

        public AlunoAberto(string numeroPECE, string nome, string email, CursoAberto curso)
            : base(numeroPECE, nome, email)
        {
            this.Curso = curso;
            this.OferecimentosCursados = new List<Oferecimento>();
            this.DisciplinasPreSelecionadas = new List<Disciplina>();
            this.Trocas = new List<TrocaDisciplina>();
        }

        [JoinedKey]
        public override int Id { get { return base.Id; } }

        [BelongsTo]
        public CursoAberto Curso { get; set; }

        private IList<Oferecimento> oferecimentosCursados;
        [HasAndBelongsToMany(Table = "oferecimento_aluno", ColumnKey = "aluno_id", ColumnRef = "oferecimento_id", Inverse = true)]
        public IList<Oferecimento> OferecimentosCursados
        {
            get
            {
                return new List<Oferecimento>(oferecimentosCursados).AsReadOnly();
            }
            private set { oferecimentosCursados = value; }
        }
        /// <summary>
        /// Obtém a lista de disciplinas que este aluno pré-selecionou quando se inscreveu no PECE.
        /// </summary>
        [HasAndBelongsToMany(Table = "disciplinas_aluno", ColumnKey = "aluno_id", ColumnRef = "disciplina_id")]
        public IList<Disciplina> DisciplinasPreSelecionadas { get; set; }



        public IEnumerable<Disciplina> DisciplinasPreSelecionadasAtivas
        {
            get
            {
                var disciplinasPreselecionadas = from d in DisciplinasPreSelecionadas
                                                 where d.Ativado == true
                                                 select d;
                return disciplinasPreselecionadas;

            }

        }
        // TODO: vai mostrar todas ou soh as ativas
        // por exemplo, na tela de editar Aluno
        // pensar tb no caso de disciplina removida e aluno vai fazer uma rematricula


        [HasMany(Cascade = ManyRelationCascadeEnum.SaveUpdate)]
        public IList<TrocaDisciplina> Trocas { get; set; }

        /// <summary>
        /// Troca uma disciplina da lista de disciplinas pré-inscritas por outra, que
        /// não faz parte dessa lista. A disciplina a ser trocoda não pode ter sido
        /// cursda por este aluno.
        /// </summary>
        /// <param name="original">A disciplina a ser trocada</param>
        /// <param name="nova">A disciplina que irá substituir</param>
        /// <exception cref="ArgumentException"></exception>
        public void TrocarDisciplina(Disciplina original, Disciplina nova)
        {
            #region verificação de sanidade dos argumentos
            if (!DisciplinasPreSelecionadas.Contains(original))
            {
                throw new ArgumentException(String.Format("A disciplina {0} não pode ser trocada pois não havia sido pré-selecionada", original.ToString()));
            }
            if (JáCursou(original))
            {
                throw new ArgumentException(String.Format("A disciplina {0} já foi cursada por este aluno", original.ToString()));
            }
            if (DisciplinasPreSelecionadas.Contains(nova))
            {
                throw new ArgumentException(String.Format("A disciplina {0} já havia sido pré-selecionada por este aluno", nova.ToString()));
            }
            #endregion

            // nesse ponto, original foi pré-selecionada e não foi cursada
            // nova não foi pré-selecionada
            // trocamos ambas na lista de DisciplinasPréSelecionadas
            this.DisciplinasPreSelecionadas.Remove(original);
            this.DisciplinasPreSelecionadas.Add(nova);
            this.Trocas.Add(new TrocaDisciplina(this, DateTime.Now, original, nova));
        }

        public bool JáCursou(Disciplina disciplina)
        {
            return GetDisciplinasCursadas().Contains(disciplina);
        }

        /// <summary>
        /// Obtém todas as disciplinas que este aluno pré-selecionou, mas ainda não cursou.
        /// Define-se cursada uma disciplina para a qual haja um oferecimento matriculado,
        /// desde que esse oferecimento não diga respeito ao período de rematrícula vigente.
        /// </summary>
        /// <returns></returns>
        public IEnumerable<Disciplina> GetDisciplinasNaoCursadas()
        {
            PeriodoRematricula periodoAtual = PeriodoRematricula.GetPeriodoVigente();
            return GetDisciplinasNaoCursadas(periodoAtual);
        }

        public IEnumerable<Disciplina> GetDisciplinasCursadas()
        {
            PeriodoRematricula periodoAtual = PeriodoRematricula.GetPeriodoVigente();
            return GetDisciplinasCursadas(periodoAtual);
        }

        private IEnumerable<Disciplina> GetDisciplinasNaoCursadas(PeriodoRematricula periodoAtual)
        {
            var disciplinasCursadas = GetDisciplinasCursadas(periodoAtual);
            return DisciplinasPreSelecionadas.Where(d => !disciplinasCursadas.Contains(d));
        }

        private IEnumerable<Disciplina> GetDisciplinasCursadas(PeriodoRematricula periodoAtual)
        {
            var disciplinasCursadas = from of in OferecimentosCursados
                                      where of.Periodo != periodoAtual
                                      select of.Disciplina;
            return disciplinasCursadas;
        }
        /// <summary>
        /// Obtém todos os oferecimentos que podem ser cursados no período vigente.
        /// </summary>
        /// <returns></returns>
        public Oferecimento[] GetOferecimentosCursáveis()
        {
            PeriodoRematricula periodoVigente = PeriodoRematricula.GetPeriodoVigente();
            var disciplinasNãoCursadas = GetDisciplinasNaoCursadas(periodoVigente);
            return periodoVigente.Oferecimentos.Where(of => disciplinasNãoCursadas.Contains(of.Disciplina)).ToArray();
        }

        /// <summary>
        /// Obtém todos os oferecimentos que este aluno decidiu cursar no período de rematrícula
        /// que está aberto agora. Lança uma exceção se não houver período de rematrícula
        /// aberto.
        /// </summary>
        public Oferecimento[] GetOferecimentosMatriculadosPeriodoVigente()
        {
            PeriodoRematricula periodoVigente = PeriodoRematricula.GetPeriodoVigente();
            return this.OferecimentosCursados.Where(of => of.Periodo == periodoVigente).ToArray();
        }

        /// <summary>
        /// Cancela todas as matrículas deste aluno.
        /// </summary>
        /// <exception cref=""></exception>
        public void CancelaMatriculas()
        {
            PeriodoRematricula periodoVigente = PeriodoRematricula.GetPeriodoVigente();
            this.OferecimentosCursados.Where(of => of.Periodo == periodoVigente).ForEach(of => of.CancelaMatrícula(this));
        }

        /// <summary>
        /// Realiza a matrícula num oferecimento.
        /// </summary>
        /// <param name="oferecimento"></param>
        /// <exception cref="ArgumentException">O aluno não pré-selecionou esta disciplina</exception>
        /// <exception cref="EsgotadoException">Não há mais vagas no oferecimento</exception>
        public void Matricula(Oferecimento oferecimento)
        {
            oferecimento.Matricula(this);
        }

        public override Curso GetCurso()
        {
            return Curso;
        }

        # region Métodos estáticos do ActiveRecord
        public static new void DeleteAll()
        {
            DeleteAll(typeof(AlunoAberto));
        }

        public static new AlunoAberto[] FindAll()
        {
            return (AlunoAberto[])FindAllByProperty(typeof(AlunoAberto), "Ativado", true);
        }

        public static new AlunoAberto Find(int id)
        {
            return (AlunoAberto)FindByPrimaryKey(typeof(AlunoAberto), id);
        }

        public new static AlunoAberto[] FindAllByProperty(string property, object value)
        {

            return (AlunoAberto[])FindAllByProperty(typeof(AlunoAberto), property, value);
        }

        #endregion
    }
}
